https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
题目 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
1 2 3 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc" ,所以其长度为 3。
示例 2:
1 2 3 输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b" ,所以其长度为 1。
示例 3:
1 2 3 4 输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke" ,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路 关键点:
无重复字符
最长子串 个人解题思路,最长无重复字符子串必然会有一个起点,这个起点我们通过遍历字符串依次从字串首位到末尾,终点我们通过比较依次添加第i位后字符判断当前子串是否存在重复字符来判断。用一个容器来存储当前正在考察的子串,使用TreeSet来存储分析来每个无重复字符的子串长度,主要是TreeSet可以帮我排序。
解题 第一次提交 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int lengthOfLongestSubstring (String s) { TreeSet<Integer> tempRes = new TreeSet<>(); Stack<Character> result = new Stack<>(); char [] inputs = s.toCharArray(); for (int i = 0 ; i < inputs.length; i++) { char current = inputs[i]; if (result.contains(current)) { tempRes.add(result.size()); result.clear(); } result.push(current); } tempRes.add(result.size()); tempRes.comparator(); return tempRes.last(); } }
解题错误:测试用例”dvdf” 第一次提交,我采用堆栈存储当前正在考察的子串,依次添加字符,若出现与堆栈相同的字符,停止添加字符,保存当前堆栈长度,清空堆栈。然后从当前终点开始寻找下一个子串。 这个思路考虑不周全,是有问题的。出现重复字符时,不能清空堆栈并且循环的下一个点应该从当前起点的下一个字符开始。以测试用例“dvdf”为例子,看看问题处在哪里
i
堆栈
长度
push(‘d’)
0
{d}
1
push(‘v’)
1
{d, v}
2
push(‘d’)
出现重复字符串,保存当前长度2,清空堆栈
push(‘d’)
2
{d}
1
push(‘f’)
3
{d,f}
2
循环结束,保存堆栈长度
第二次提交 总结第一次提交的问题,重新提交。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int lengthOfLongestSubstring (String s) { TreeSet<Integer> tempRes = new TreeSet<>(); Queue<Character> result = new LinkedList<>(); char [] inputs = s.toCharArray(); for (int i = 0 ; i < inputs.length; i++) { char current = inputs[i]; if (result.contains(current)) { tempRes.add(result.size()); result.remove(); i--; } else { result.add(current); } } tempRes.add(result.size()); tempRes.comparator(); return tempRes.last(); } }
第二次提交,我把存储子串的数据结构改成来队列,这样,当出现重复子串时,可以快速的移除队头,通过索引减1的方式,来保证到下一个字符时,队列中不会有重复字符。这里有个可优化的点,直接找到当前重复的字符在队列中的位置,然后直接处理。同样是以第一次提交报错的测试用例,看看执行步骤:
i
队列
长度
add(‘d’)
0
{d}
1
add(‘v’)
1
{d, v}
2
add(‘d’)
出现重复字符串,保存当前长度2,移除队头,–i
add(‘d’)
2
{v, d}
2
add(‘f’)
3
{v,d,f}
3
循环结束,保存堆栈长度
第三次提交 优化后的算法,采用动态规划的方式 设f(i)是从左到右在第i个位置上时 当前最长不重复子串的长度 则分两种情况: (1)第i位上的字母在左侧从未出现 f(i)=f(i-1)+1 (2)在左侧出现过,此时计算上一次该字母出现位置和i之间的距离 gap 第一种:gap<=f(i-1) 时 f(i)=gap,此时上一次出现的位置在上一个最长不重复子串内部 第二种:gap>f(i-1) 时 f(i)=f(i-1)+1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int lengthOfLongestSubstring (String s) { if (s.isEmpty()) { return 0 ; } int [] position = new int [256 ]; for (int i = 0 ; i < position.length; i++) { position[i] = -1 ; } int max = 0 ; int cur = 0 ; for (int i = 0 ; i < s.length(); i++) { int pos = s.charAt(i); if (position[pos] < 0 ) { cur += 1 ; } else { int gap = i - position[pos]; if (cur >= gap) { cur = gap; } else { cur += 1 ; } } position[pos] = i; max = Math.max(max, cur); } return max; } }
以 “pwwkew” 为例子,执行步骤:
i
cur
max
gap
0
1
1
1
2
2
2
1
2
1
3
2
2
4
3
3
5
3
3
3
总结
合适的数据结构事半功倍
针对字符串操作的题目,边界和特殊情况一定要多考虑。